import Delaunator from "delaunator";
import Path from "./path";
import Polygon from "./polygon";
import Voronoi from "./voronoi";
const tau : number = 2 * Math.PI;
const pow : (x1 : number, x2 : number) => number = Math.pow;
function pointX (p : any) : any {
    return p [0];
}
function pointY (p : any) : any {
    return p [1];
}
function collinear (d : Delaunator) : boolean {
    const {triangles, coords} : any = d;
    for (let i : number = 0; i < triangles.length; i += 3) {
        const a : number = 2 * triangles [i];
        const b : number = 2 * triangles [i + 1];
        const c : number = 2 * triangles [i + 2];
        const cross : any = (coords [c] - coords [a]) * (coords [b + 1] - coords [a + 1]) - (coords [b] - coords [a]) * (coords [c + 1] - coords [a + 1]);
        if (cross > 1e-10) return false;
    }
    return true;
}
function jitter (x : any, y : any, r : number) : any [] {
    return [x + Math.sin (x + y) * r, y + Math.cos (x - y) * r];
}
export default class Delaunay {
    inedges ! : Int32Array;
    _hullIndex ! : Int32Array;
    points ! : any;
    collinear ! : any;
    _delaunator ! : Delaunator;
    hull ! : any;
    triangles ! : any;
    halfedges ! : any;
    static from (points : any, fx : any = pointX, fy : any = pointY, that : any) : Delaunay {
        return new Delaunay ("length" in points ? flatArray (points, fx, fy, that) : Float64Array.from (flatIterable (points, fx, fy, that)));
    }
    constructor (points : any) {
        this._delaunator = new Delaunator (points);
        this.inedges = new Int32Array (points.length / 2);
        this._hullIndex = new Int32Array (points.length / 2);
        this.points = this._delaunator.coords;
        this._init ();
    }
    update () : Delaunay {
        this._delaunator.update ();
        this._init ();
        return this;
    }
    _init () : any {
        const d : Delaunator = this._delaunator;
        const points : any [] = this.points;
        if (d.hull && d.hull.length > 2 && collinear (d)) {
            this.collinear = Int32Array.from ({
                length : points.length / 2
            }, (_ : any, i : any) : any => {
                    return i;
                }).sort ((i : any, j : any) : number => {
                    return points [2 * i] - points [2 * j] || points [2 * i + 1] - points [2 * j + 1];
                });
            const e : any = this.collinear [0];
            const f : any = this.collinear [this.collinear.length - 1];
            const bounds : any [] = [points [2 * e], points [2 * e + 1], points [2 * f], points [2 * f + 1]];
            const r : number = 1e-8 * Math.hypot (bounds [3] - bounds [1], bounds [2] - bounds [0]);
            for (let i : number = 0, n : number = points.length / 2; i < n; ++ i) {
                const p : any [] = jitter (points [2 * i], points [2 * i + 1], r);
                points [2 * i] = p [0];
                points [2 * i + 1] = p [1];
            }
            this._delaunator = new Delaunator (points);
        } else {
            delete this.collinear;
        }
        const halfedges : number [] = this.halfedges = this._delaunator.halfedges;
        const hull : any = this.hull = this._delaunator.hull;
        const triangles : any = this.triangles = this._delaunator.triangles;
        const inedges : number [] = this.inedges.fill (- 1);
        const hullIndex : number [] = this._hullIndex.fill (- 1);
        for (let e : number = 0, n : number = halfedges.length; e < n; ++ e) {
            const p : any = triangles [e % 3 === 2 ? e - 2 : e + 1];
            if (halfedges [e] === - 1 || inedges [p] === - 1) inedges [p] = e;
        }
        for (let i : number = 0, n : number = hull.length; i < n; ++ i) {
            hullIndex [hull [i]] = i;
        }
        if (hull.length <= 2 && hull.length > 0) {
            this.triangles = new Int32Array (3).fill (- 1);
            this.halfedges = new Int32Array (3).fill (- 1);
            this.triangles [0] = hull [0];
            inedges [hull [0]] = 1;
            if (hull.length === 2) {
                inedges [hull [1]] = 0;
                this.triangles [1] = hull [1];
                this.triangles [2] = hull [1];
            }
        }
    }
    voronoi (bounds : any) : Voronoi {
        return new Voronoi (this, bounds);
    }
    * neighbors (i : any) : any {
        const {inedges, hull, _hullIndex, halfedges, triangles, collinear} : any = this;
        if (collinear) {
            const l : any = collinear.indexOf (i);
            if (l > 0) yield collinear [l - 1];
            if (l < collinear.length - 1) yield collinear [l + 1];
            return;
        }
        const e0 : number = inedges [i];
        if (e0 === - 1) return;
        let e : number = e0;
        let p0 : number = - 1;
        do {
            yield p0 = triangles [e];
            e = e % 3 === 2 ? e - 2 : e + 1;
            if (triangles [e] !== i) return;
            e = halfedges [e];
            if (e === - 1) {
                const p : number = hull [(_hullIndex [i] + 1) % hull.length];
                if (p !== p0) yield p;
                return;
            }
        }
        while (e !== e0);
    }
    find (x : number, y : number, i : number = 0) : number {
        if ((x = (+ x), x !== x) || (y = (+ y), y !== y)) return - 1;
        const i0 : number = i;
        let c ! : number;
        while ((c = this._step (i, x, y)) >= 0 && c !== i && c !== i0) i = c;
        return c;
    }
    _step (i : number, x : number, y : number) : number {
        const {inedges, hull, _hullIndex, halfedges, triangles, points} : any = this;
        if (inedges [i] === - 1 || ! points.length) return (i + 1) % (points.length >> 1);
        let c : number = i;
        let dc : number = pow (x - points [i * 2], 2) + pow (y - points [i * 2 + 1], 2);
        const e0 : number = inedges [i];
        let e : number = e0;
        do {
            let t : number = triangles [e];
            const dt : number = pow (x - points [t * 2], 2) + pow (y - points [t * 2 + 1], 2);
            if (dt < dc) dc = dt, c = t;
            e = e % 3 === 2 ? e - 2 : e + 1;
            if (triangles [e] !== i) break;
            e = halfedges [e];
            if (e === - 1) {
                e = hull [(_hullIndex [i] + 1) % hull.length];
                if (e !== t) {
                    if (pow (x - points [e * 2], 2) + pow (y - points [e * 2 + 1], 2) < dc) return e;
                }
                break;
            }
        }
        while (e !== e0);
        return c;
    }
    render (context : Path) : any {
        const buffer : Path | undefined = context == null ? context = new Path () : undefined;
        const {points, halfedges, triangles} : any = this;
        for (let i : number = 0, n : number = halfedges.length; i < n; ++ i) {
            const j : any = halfedges [i];
            if (j < i) continue;
            const ti : number = triangles [i] * 2;
            const tj : number = triangles [j] * 2;
            context.moveTo (points [ti], points [ti + 1]);
            context.lineTo (points [tj], points [tj + 1]);
        }
        this.renderHull (context);
        return buffer && buffer.value ();
    }
    renderPoints (context : any, r : any) : any {
        if (r === undefined && (! context || typeof context.moveTo !== "function")) r = context, context = null;
        r = r == undefined ? 2 : (+ r);
        const buffer : Path | undefined = context == null ? context = new Path () : undefined;
        const {points} : any = this;
        for (let i : number = 0, n : number = points.length; i < n; i += 2) {
            const x : any = points [i];
            const y : any = points [i + 1];
            context.moveTo (x + r, y);
            context.arc (x, y, r, 0, tau);
        }
        return buffer && buffer.value ();
    }
    renderHull (context : Polygon | Path) : any {
        const buffer : Path | undefined = context == null ? context = new Path () : undefined;
        const {hull, points} : any = this;
        const h : number = hull [0] * 2;
        const n : number = hull.length;
        context.moveTo (points [h], points [h + 1]);
        for (let i : number = 1; i < n; ++ i) {
            const h : number = 2 * hull [i];
            context.lineTo (points [h], points [h + 1]);
        }
        context.closePath ();
        return buffer && buffer.value ();
    }
    hullPolygon () : any {
        const polygon : Polygon = new Polygon ();
        this.renderHull (polygon);
        return polygon.value ();
    }
    renderTriangle (i : number, context : Polygon) : any {
        const buffer : Path | undefined = context == null ? context = new Path () : undefined;
        const {points, triangles} : any = this;
        const t0 : number = triangles [i *= 3] * 2;
        const t1 : number = triangles [i + 1] * 2;
        const t2 : number = triangles [i + 2] * 2;
        context.moveTo (points [t0], points [t0 + 1]);
        context.lineTo (points [t1], points [t1 + 1]);
        context.lineTo (points [t2], points [t2 + 1]);
        context.closePath ();
        return buffer && buffer.value ();
    }
    * trianglePolygons () : any {
        const {triangles} : any = this;
        for (let i : number = 0, n : number = triangles.length / 3; i < n; ++ i) {
            yield this.trianglePolygon (i);
        }
    }
    trianglePolygon (i : number) : any {
        const polygon : Polygon = new Polygon ();
        this.renderTriangle (i, polygon);
        return polygon.value ();
    }
}
function flatArray (points : any, fx : any, fy : any, that : any) : Float64Array {
    const n : number = points.length;
    const array : Float64Array = new Float64Array (n * 2);
    for (let i : number = 0; i < n; ++ i) {
        const p : any = points [i];
        array [i * 2] = fx.call (that, p, i, points);
        array [i * 2 + 1] = fy.call (that, p, i, points);
    }
    return array;
}
function * flatIterable (points : any, fx : any, fy : any, that : any) : Generator <any, any, any> {
    let i : number = 0;
    for (const p of points) {
        yield fx.call (that, p, i, points);
        yield fy.call (that, p, i, points);
        ++ i;
    }
}
